package asruby.runtime.callsite
{
import asruby.asupport.RuntimeHelpers;
import asruby.lang.RubyClass;
import asruby.lang.RubyException;
import asruby.lang.RubyObject;
import asruby.runtime.Block;
import asruby.runtime.CallSite;
import asruby.runtime.CallType;
import asruby.runtime.IRubyObject;
import asruby.runtime.MethodIndex;
import asruby.runtime.ThreadContext;
import asruby.runtime.methods.DynamicMethod;
	
	
public class InlineCachingCallSite extends CallSite  {
    var cachedMethod:DynamicMethod;
    var cachedType:RubyClass;
    
    public function InlineCachingCallSite(methodName:String, callType:CallType){
        super(MethodIndex.getIndex(methodName), methodName, callType);
    }
    
    protected function  cacheAndCall(selfType:RubyClass, block:Block, args:Array, context:ThreadContext, self:IRubyObject):IRubyObject{
        var method: DynamicMethod =  selfType.searchMethod(methodName);

        if (method.isUndefined() || ( !(methodName== "method_missing") && !method.isCallableFrom(context.getFrameSelf(), callType))) {
            return RuntimeHelpers.callMethodMissing2(context, self, method, methodName, args, context.getFrameSelf(), callType, block);
        }

        cachedMethod = method;
        cachedType = selfType;

        selfType.getRuntime().getCacheMap().add(method, this);

        return method.call(context, self, selfType, methodName, args, block);
    }

    protected function cacheAndCall2(selfType:RubyClass, args:Array, context:ThreadContext, self:IRubyObject):IRubyObject{
        var method: DynamicMethod =  selfType.searchMethod(methodName);

        if (method.isUndefined() || (!(methodName=="method_missing") && !method.isCallableFrom(context.getFrameSelf(), callType))) {
            return RuntimeHelpers.callMethodMissing2(context, self, method, methodName, args, context.getFrameSelf(), callType, Block.NULL_BLOCK);
        }

        cachedMethod = method;
        cachedType = selfType;

        selfType.getRuntime().getCacheMap().add(method, this);

        return method.call(context, self, selfType, methodName, args, Block.NULL_BLOCK);
    }

    protected  function cacheAndCall3(selfType:RubyClass, context:ThreadContext, self:IRubyObject):IRubyObject{
        var method: DynamicMethod =  selfType.searchMethod(methodName);

        if (method.isUndefined() || (!(methodName=="method_missing") && !method.isCallableFrom(context.getFrameSelf(), callType))) {
            return RuntimeHelpers.callMethodMissing2(context, self, method, methodName, 
            	RubyObject.NULL_ARRAY, context.getFrameSelf(), callType, Block.NULL_BLOCK);
        }

        cachedMethod = method;
        cachedType = selfType;

        selfType.getRuntime().getCacheMap().add(method, this);

        return method.call1(context, self, selfType, methodName);
    }

    protected function  cacheAndCall4(selfType:RubyClass, block:Block, context:ThreadContext, self:IRubyObject):IRubyObject{
        var method: DynamicMethod =  selfType.searchMethod(methodName);

        if (method.isUndefined() || (!(methodName=="method_missing") && !method.isCallableFrom(context.getFrameSelf(), callType))) {
            return RuntimeHelpers.callMethodMissing2(context, self, method, methodName, 
            	RubyObject.NULL_ARRAY, context.getFrameSelf(), callType, block);
        }

        cachedMethod = method;
        cachedType = selfType;

        selfType.getRuntime().getCacheMap().add(method, this);

        return method.call2(context, self, selfType, methodName, block);
    }

    protected function  cacheAndCall5(selfType:RubyClass, context:ThreadContext, self:IRubyObject, arg:IRubyObject):IRubyObject{
        var method: DynamicMethod =  selfType.searchMethod(methodName);

        if (method.isUndefined() || (!(methodName=="method_missing") && !method.isCallableFrom(context.getFrameSelf(), callType))) {
            return RuntimeHelpers.callMethodMissing2(context, self, method, methodName, [arg], context.getFrameSelf(), 
            	callType, Block.NULL_BLOCK);
        }

        cachedMethod = method;
        cachedType = selfType;

        selfType.getRuntime().getCacheMap().add(method, this);

        return method.call3(context, self, selfType, methodName, arg);
    }

    protected  function cacheAndCall6(selfType:RubyClass, block:Block, context:ThreadContext, self:IRubyObject, arg:IRubyObject):IRubyObject{
        var method: DynamicMethod =  selfType.searchMethod(methodName);

        if (method.isUndefined() || (!(methodName=="method_missing") && !method.isCallableFrom(context.getFrameSelf(), callType))) {
            return RuntimeHelpers.callMethodMissing2(context, self, method, methodName, [arg], context.getFrameSelf(), callType, block);
        }

        cachedMethod = method;
        cachedType = selfType;

        selfType.getRuntime().getCacheMap().add(method, this);

        return method.call4(context, self, selfType, methodName, arg, block);
    }

    protected  function cacheAndCall7(selfType:RubyClass, context:ThreadContext, self:IRubyObject, arg1:IRubyObject, arg2:IRubyObject):IRubyObject{
        var method: DynamicMethod =  selfType.searchMethod(methodName);

        if (method.isUndefined() || (!(methodName=="method_missing") && !method.isCallableFrom(context.getFrameSelf(), callType))) {
            return RuntimeHelpers.callMethodMissing2(context, self, method, methodName, [arg1,arg2], context.getFrameSelf(), callType, Block.NULL_BLOCK);
        }

        cachedMethod = method;
        cachedType = selfType;

        selfType.getRuntime().getCacheMap().add(method, this);

        return method.call5(context, self, selfType, methodName, arg1, arg2);
    }

    protected function  cacheAndCall8(selfType:RubyClass, block:Block, context:ThreadContext, self:IRubyObject, arg1:IRubyObject, arg2:IRubyObject):IRubyObject{
        var method: DynamicMethod =  selfType.searchMethod(methodName);

        if (method.isUndefined() || (!(methodName=="method_missing") && !method.isCallableFrom(context.getFrameSelf(), callType))) {
            return RuntimeHelpers.callMethodMissing2(context, self, method, methodName, [arg1,arg2], context.getFrameSelf(), callType, block);
        }

        cachedMethod = method;
        cachedType = selfType;

        selfType.getRuntime().getCacheMap().add(method, this);

        return method.call6(context, self, selfType, methodName, arg1, arg2, block);
    }

    protected function  cacheAndCall9(selfType:RubyClass, context:ThreadContext, self:IRubyObject, arg1:IRubyObject, arg2:IRubyObject, arg3:IRubyObject):IRubyObject{
        var method: DynamicMethod =  selfType.searchMethod(methodName);

        if (method.isUndefined() || (!(methodName=="method_missing") && !method.isCallableFrom(context.getFrameSelf(), callType))) {
            return RuntimeHelpers.callMethodMissing2(context, self, method, methodName, [arg1,arg2,arg3], context.getFrameSelf(), callType, Block.NULL_BLOCK);
        }

        cachedMethod = method;
        cachedType = selfType;

        selfType.getRuntime().getCacheMap().add(method, this);

        return method.call7(context, self, selfType, methodName, arg1, arg2, arg3);
    }

    protected  function cacheAndCall10(selfType:RubyClass, block:Block, context:ThreadContext, self:IRubyObject, arg1:IRubyObject, arg2:IRubyObject, arg3:IRubyObject):IRubyObject{
        var method: DynamicMethod =  selfType.searchMethod(methodName);

        if (method.isUndefined() || (!(methodName=="method_missing") && !method.isCallableFrom(context.getFrameSelf(), callType))) {
            return RuntimeHelpers.callMethodMissing2(context, self, method, methodName, [arg1,arg2,arg3], context.getFrameSelf(), callType, block);
        }

        cachedMethod = method;
        cachedType = selfType;

        selfType.getRuntime().getCacheMap().add(method, this);

        return method.call8(context, self, selfType, methodName, arg1, arg2, arg3, block);
    }
    
    public function  removeCachedMethod():void{
        cachedType = null;
        cachedMethod = null;
    }
    
    private function  pollThreadEvents(context:ThreadContext):void{
        //if ((CallSite.callCount++ & 0xFF) == 0) context.pollThreadEvents();
    }
    
    public override function call4(context:ThreadContext, self:IRubyObject, args:Array):IRubyObject{
        pollThreadEvents(context);

        var selfType: RubyClass =  self.getMetaClass();
        
        if (cachedType == selfType && cachedMethod != null) {
            return cachedMethod.call(context, self, selfType, methodName, args, Block.NULL_BLOCK);
        }

        return cacheAndCall2(selfType, args, context, self);
    }
    
    public override function  call9(context:ThreadContext, self:IRubyObject, args:Array, block:Block):IRubyObject{
        pollThreadEvents(context);

        try {
            var selfType: RubyClass =  self.getMetaClass();

            if (cachedType == selfType && cachedMethod != null) {
                return cachedMethod.call(context, self, selfType, methodName, args, block);
            }

            return cacheAndCall(selfType, block, args, context, self);
        } catch (bj:RubyException) {
            return handleBreakJump(bj, block);
        }
        return null; 
        /* catch (rj:JumpException.RetryJump) {
            throw context.getRuntime().newLocalJumpError("retry", context.getRuntime().getNil(), "retry outside of rescue not yet supported");
        } catch (soe:StackOverflowError) {
            throw context.getRuntime().newSystemStackError("stack level too deep");
        } */
    }
    
    public override function  call(context:ThreadContext, self:IRubyObject):IRubyObject{
        pollThreadEvents(context);

        var selfType: RubyClass =  self.getMetaClass();

        if (cachedType == selfType && cachedMethod != null) {
            return cachedMethod.call1(context, self, selfType, methodName);
        }

        return cacheAndCall3(selfType, context, self);
    }
    
    public override function  call5(context:ThreadContext, self:IRubyObject, block:Block):IRubyObject{
        pollThreadEvents(context);

        try {
            var selfType: RubyClass =  self.getMetaClass();

            if (cachedType == selfType && cachedMethod != null) {
                return cachedMethod.call2(context, self, selfType, methodName, block);
            }

            return cacheAndCall4(selfType, block, context, self);
        } catch (bj:RubyException) {
            return handleBreakJump(bj, block);
        }/*  catch (rj:JumpException.RetryJump) {
            throw context.getRuntime().newLocalJumpError("retry", context.getRuntime().getNil(), "retry outside of rescue not yet supported");
        } catch (soe:StackOverflowError) {
            throw context.getRuntime().newSystemStackError("stack level too deep");
        } */
        return null;
    }
    
    public override function  call1(context:ThreadContext, self:IRubyObject, arg1:IRubyObject):IRubyObject{
        pollThreadEvents(context);

        var selfType: RubyClass =  self.getMetaClass();

        if (cachedType == selfType && cachedMethod != null) {
            return cachedMethod.call3(context, self, selfType, methodName, arg1);
        }

        return cacheAndCall5(selfType, context, self, arg1);
    }
    
    public override function call6(context:ThreadContext, self:IRubyObject, arg1:IRubyObject, block:Block):IRubyObject{
        pollThreadEvents(context);

        try {
            var selfType: RubyClass =  self.getMetaClass();

            if (cachedType == selfType && cachedMethod != null) {
                return cachedMethod.call4(context, self, selfType, methodName, arg1, block);
            }

            return cacheAndCall6(selfType, block, context, self, arg1);
        } catch (bj:RubyException) {
            return handleBreakJump(bj, block);
        }
        return null; 
        /* catch (rj:JumpException.RetryJump) {
            throw context.getRuntime().newLocalJumpError("retry", context.getRuntime().getNil(), "retry outside of rescue not yet supported");
        } catch (soe:StackOverflowError) {
            throw context.getRuntime().newSystemStackError("stack level too deep");
        } */
    }
    
    public override function call2(context:ThreadContext, self:IRubyObject, arg1:IRubyObject, arg2:IRubyObject):IRubyObject{
        pollThreadEvents(context);

        var selfType: RubyClass =  self.getMetaClass();

        if (cachedType == selfType && cachedMethod != null) {
            return cachedMethod.call5(context, self, selfType, methodName, arg1, arg2);
        }

        return cacheAndCall7(selfType, context, self, arg1, arg2);
    }
    
    public override function call7(context:ThreadContext, self:IRubyObject, arg1:IRubyObject, arg2:IRubyObject, block:Block):IRubyObject{
        pollThreadEvents(context);

        try {
            var selfType: RubyClass =  self.getMetaClass();

            if (cachedType == selfType && cachedMethod != null) {
                return cachedMethod.call6(context, self, selfType, methodName, arg1, arg2, block);
            }

            return cacheAndCall8(selfType, block, context, self, arg1, arg2);
        } catch (bj:RubyException) {
            return handleBreakJump(bj, block);
        }
        return null;
        /*  catch (rj:JumpException.RetryJump) {
            throw context.getRuntime().newLocalJumpError("retry", context.getRuntime().getNil(), "retry outside of rescue not yet supported");
        } catch (soe:StackOverflowError) {
            throw context.getRuntime().newSystemStackError("stack level too deep");
        } */
    }
    
    public override function  call3(context:ThreadContext, self:IRubyObject, arg1:IRubyObject, arg2:IRubyObject, arg3:IRubyObject):IRubyObject{
        pollThreadEvents(context);

        var selfType: RubyClass =  self.getMetaClass();

        if (cachedType == selfType && cachedMethod != null) {
            return cachedMethod.call7(context, self, selfType, methodName, arg1, arg2, arg3);
        }

        return cacheAndCall9(selfType, context, self, arg1, arg2, arg3);
    }
    
    public override function  call8(context:ThreadContext, self:IRubyObject, arg1:IRubyObject, arg2:IRubyObject, arg3:IRubyObject, block:Block):IRubyObject{
        pollThreadEvents(context);

        try {
            var selfType: RubyClass =  self.getMetaClass();

            if (cachedType == selfType && cachedMethod != null) {
                return cachedMethod.call8(context, self, selfType, methodName, arg1, arg2, arg3, block);
            }

            return cacheAndCall10(selfType, block, context, self, arg1, arg2, arg3);
        } catch (bj:RubyException) {
            return handleBreakJump(bj, block);
        }
        return null; 
        /* catch (rj:JumpException.RetryJump) {
            throw context.getRuntime().newLocalJumpError("retry", context.getRuntime().getNil(), "retry outside of rescue not yet supported");
        } catch (soe:StackOverflowError) {
            throw context.getRuntime().newSystemStackError("stack level too deep");
        } */
    }

    private function  handleBreakJump(bj:Object, block:Block):IRubyObject{
        // JRUBY-530, Kernel#loop case:
        if (bj.isBreakInKernelLoop()) {
            // consume and rethrow or just keep rethrowing?
            if (block.getBody() == bj.getTarget()) {
                bj.setBreakInKernelLoop(false);
            }
            throw bj;
        }

        return bj.getValue();
    }
}

}